// src/pages/test/useCallbackTest/index.tsx - useCallback Hook 详解页面
import React, { useState, useCallback, useMemo } from 'react';
import { Card, Button, Space, Typography, Divider, InputNumber, List } from 'antd';

const { Title, Paragraph } = Typography;

/**
 * 模拟一个昂贵的计算函数
 * @param num - 输入数字
 * @returns 计算结果
 */
const expensiveCalculation = (num: number): number => {
  console.log('执行昂贵的计算...', num);
  // 模拟一个耗时的操作
  let result = 0;
  for (let i = 0; i < 10000000; i++) {
    result += num;
  }
  return result;
};

/**
 * 子组件 - 展示用户信息
 * @param props - 组件属性
 * @param props.onDelete - 删除用户的回调函数
 * @param props.name - 用户名
 * @param props.id - 用户ID
 */
const UserItem: React.FC<{ onDelete: (id: number) => void; name: string; id: number }> = ({ 
  onDelete, 
  name, 
  id 
}) => {
  return (
    <div style={{ padding: '10px', border: '1px solid #ddd', margin: '5px', borderRadius: '4px' }}>
      <span>{name} (ID: {id})</span>
      <Button 
        danger 
        size="small" 
        style={{ float: 'right' }}
        onClick={() => onDelete(id)}
      >
        删除
      </Button>
      <div style={{ clear: 'both' }}></div>
    </div>
  );
};

const UseCallbackTestPage: React.FC = () => {
  // 计数器状态
  const [count, setCount] = useState<number>(0);
  
  // 用户列表状态
  const [users, setUsers] = useState<Array<{id: number, name: string}>>([
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' },
    { id: 3, name: 'Charlie' }
  ]);
  
  // 数字输入状态
  const [number, setNumber] = useState<number>(5);
  
  // 使用 useMemo 缓存昂贵计算的结果
  const expensiveValue = useMemo(() => {
    return expensiveCalculation(number);
  }, [number]); // 只有当 number 改变时才重新计算
  
  /**
   * 不使用 useCallback 的普通函数
   * 每次组件重新渲染时都会创建一个新的函数实例
   */
  const handleIncrementWithoutCallback = () => {
    setCount(c => c + 1);
  };
  
  /**
   * 使用 useCallback 包装的函数
   * 只有当依赖项改变时才会重新创建函数实例
   */
  const handleIncrementWithCallback = useCallback(() => {
    setCount(c => c + 1);
  }, []); // 空依赖数组表示函数永远不会重新创建
  
  /**
   * 添加用户的功能 - 不使用 useCallback
   */
  const handleAddUserWithoutCallback = () => {
    const newUser = {
      id: Date.now(), // 简单地使用时间戳作为 ID
      name: `User ${users.length + 1}`
    };
    setUsers([...users, newUser]);
  };
  
  /**
   * 添加用户的功能 - 使用 useCallback
   */
  const handleAddUserWithCallback = useCallback(() => {
    const newUser = {
      id: Date.now(),
      name: `User ${users.length + 1}`
    };
    setUsers(prevUsers => [...prevUsers, newUser]);
  }, [users]); // 依赖 users 数组，当 users 改变时函数会重新创建
  
  /**
   * 删除用户的功能 - 不使用 useCallback
   */
  const handleDeleteUserWithoutCallback = (id: number) => {
    setUsers(users.filter(user => user.id !== id));
  };
  
  return (
    <Card title="useCallback Hook 详解" style={{ minHeight: '100%' }}>
      <Typography>
        <Title level={2}>useCallback Hook 简介</Title>
        <Paragraph>
          useCallback 是 React 提供的一个 Hook，它用于优化性能，通过记忆函数的方式来防止在每次渲染时创建新的函数实例。
          这对于将函数作为 prop 传递给子组件时特别有用，可以避免不必要的重新渲染。
        </Paragraph>
        
        <Title level={3}>基础语法</Title>
        <Paragraph>
          <pre>
            {`const memoizedCallback = useCallback(() => {\n  doSomething(a, b);\n}, [a, b]);`}
          </pre>
        </Paragraph>
        <Paragraph>
          <ul>
            <li><code>memoizedCallback</code> - 被记忆的回调函数</li>
            <li><code>useCallback</code> - React Hook</li>
            <li><code>[a, b]</code> - 依赖数组，只有当依赖项发生变化时才会重新创建函数</li>
          </ul>
        </Paragraph>
        
        <Title level={3}>为什么需要 useCallback？</Title>
        <Paragraph>
          在 JavaScript 中，函数是引用类型。这意味着即使两个函数具有相同的逻辑，
          它们也是不同的对象，因此比较它们会产生 false：
        </Paragraph>
        <Paragraph>
          <pre>{`const fn1 = () => {};\nconst fn2 = () => {};\nconsole.log(fn1 === fn2); // false`}</pre>
        </Paragraph>
        <Paragraph>
          在 React 中，当组件的 prop 发生变化时，组件会重新渲染。如果父组件传递了一个没有使用 useCallback 包装的函数，
          那么每次父组件重新渲染时，都会创建一个新的函数实例，导致子组件认为 prop 发生了变化并重新渲染，即使函数逻辑没有改变。
        </Paragraph>
      </Typography>
      
      <Divider />
      
      {/* 计数器示例 */}
      <Title level={4}>1. 计数器示例</Title>
      <Space direction="vertical" style={{ width: '100%' }}>
        <Paragraph>
          当前计数: <strong>{count}</strong>
        </Paragraph>
        <Space>
          <Button onClick={handleIncrementWithoutCallback}>
            增加（未使用 useCallback）
          </Button>
          <Button onClick={handleIncrementWithCallback}>
            增加（使用 useCallback）
          </Button>
        </Space>
        <Paragraph>
          <Typography.Text type="secondary">
            在这个简单的例子中，两种方式的效果是一样的。但是当我们把函数传递给子组件时，差异就体现出来了。
          </Typography.Text>
        </Paragraph>
      </Space>
      
      <Divider />
      
      {/* 昂贵计算示例 */}
      <Title level={4}>2. 昂贵计算示例</Title>
      <Space direction="vertical" style={{ width: '100%' }}>
        <Paragraph>
          输入数字: 
          <InputNumber 
            min={1} 
            max={100} 
            value={number} 
            onChange={(value) => setNumber(value || 1)} 
          />
        </Paragraph>
        <Paragraph>
          昂贵计算结果: <strong>{expensiveValue}</strong>
        </Paragraph>
        <Paragraph>
          <Typography.Text type="secondary">
            这里我们使用 useMemo 来缓存昂贵的计算结果。只有当输入数字改变时，才会重新计算。
          </Typography.Text>
        </Paragraph>
      </Space>
      
      <Divider />
      
      {/* 用户列表示例 */}
      <Title level={4}>3. 用户列表示例</Title>
      <Space direction="vertical" style={{ width: '100%' }}>
        <Space>
          <Button onClick={handleAddUserWithoutCallback}>
            添加用户（未使用 useCallback）
          </Button>
          <Button onClick={handleAddUserWithCallback}>
            添加用户（使用 useCallback）
          </Button>
        </Space>
        
        <Title level={5}>用户列表:</Title>
        <List
          dataSource={users}
          renderItem={user => (
            <UserItem 
              key={user.id}
              id={user.id}
              name={user.name}
              onDelete={handleDeleteUserWithoutCallback} // 可以尝试换成 handleDeleteUserWithCallback 对比效果
            />
          )}
        />
        
        <Paragraph>
          <Typography.Text type="secondary">
            在这个例子中，UserItem 组件接收 onDelete 函数作为 prop。
            如果不使用 useCallback，每次父组件重新渲染时都会创建新的 onDelete 函数，
            这可能导致 UserItem 不必要的重新渲染。
          </Typography.Text>
        </Paragraph>
      </Space>
      
      <Divider />
      
      <Title level={3}>何时使用 useCallback？</Title>
      <Paragraph>
        <ul>
          <li>
            <strong>将函数作为 prop 传递给子组件时：</strong>
            特别是当子组件使用了 React.memo 进行优化时。
          </li>
          <li>
            <strong>函数作为其他 Hook 的依赖项时：</strong>
            比如在 useEffect、useMemo 或其他 useCallback 中使用。
          </li>
          <li>
            <strong>函数创建成本较高时：</strong>
            如果函数内部包含复杂的逻辑或者大量变量。
          </li>
        </ul>
      </Paragraph>
      
      <Title level={3}>注意事项</Title>
      <Paragraph>
        <ul>
          <li>
            <strong>不要过早优化：</strong>
            不是所有函数都需要使用 useCallback。只有在确实存在性能问题时才考虑使用。
          </li>
          <li>
            <strong>正确指定依赖项：</strong>
            依赖数组必须包含函数中使用的所有变量，否则可能会导致 bug。
          </li>
          <li>
            <strong>配合 React.memo 使用：</strong>
            useCallback 的优化效果通常需要配合 React.memo 才能发挥最大作用。
          </li>
        </ul>
      </Paragraph>
    </Card>
  );
};

export default UseCallbackTestPage;